const VueReactivity = (function () {
  const isIntegerKey = (key) => '' + parseInt(key, 10) === key;
  const hasOwn = (obj, key) => obj.hasOwnProperty(key);
  const TrackOp = {
    GET: 'GET',
  };

  const TriggerOp = {
    SET: 'SET',
    ADD: 'ADD',
  };

  const effectStack = [];
  let activeEffect;
  /**
   * WeakMap<Target, Map<key in Target, Set<Effect>>>
   */
  const targetMap = new WeakMap();

  function reactive(target) {
    const proxy = new Proxy(target, {
      get(target, key, receiver) {
        const res = Reflect.get(target, key, receiver);
        console.log('获取');
        track(target, TrackOp.GET, key);
        return res;
      },
      set(target, key, value, receiver) {
        // const a = [12];
        // a[0]
        // '0'
        const hadKey =
          Array.isArray(target) && isIntegerKey(key)
            ? Number(key) < target.length
            : hasOwn(target, key);

        const res = Reflect.set(target, key, value, receiver);

        if (hadKey) {
          trigger(target, TriggerOp.SET, key, value);
        } else {
          trigger(target, TriggerOp.ADD, key, value);
        }
        return res;
      },
    });
    return proxy;
  }

  function trigger(target, type, key, newValue) {
    const depsMap = targetMap.get(target);
    if (!depsMap) return;
    /**
     * 搜集需要重新运行的effect
     */
    const effects = new Set();

    const add = (effectToAdd) => {
      if (effectToAdd) {
        effectToAdd.forEach((effect) => {
          if (effect !== activeEffect) {
            effects.add(effect);
          }
        });
      }
    };

    if (key === 'length' && Array.isArray(target)) {
      /**
       * 直接修改length
       */
      depsMap.forEach((dep, index) => {
        if (key === 'length' || index >= newValue) {
          add(dep);
        }
      });
    } else {
      if (key !== undefined) {
        add(depsMap.get(key));
      }

      switch (type) {
        case TriggerOp.ADD:
          if (Array.isArray(target) && isIntegerKey(key)) {
            add(depsMap.get('length'));
          }
      }
    }

    effects.forEach((effect) => effect());
  }

  function track(target, type, key) {
    if (!activeEffect) {
      return;
    }

    /**
     * Map<key, Set<Effect>>
     */
    let depsMap = targetMap.get(target);

    if (!depsMap) {
      depsMap = new Map();
      targetMap.set(target, depsMap);
    }

    let dep = depsMap.get(key);
    if (!dep) {
      dep = new Set();
      depsMap.set(key, dep);
    }
    if (!dep.has(activeEffect)) {
      dep.add(activeEffect);
      activeEffect.deps.push(dep);
    }
  }

  function effect(fn, options = {}) {
    const effect = createReactiveEffect(fn);
    if (!options.lazy) {
      effect();
    }
    return effect;
  }

  function createReactiveEffect(fn, options) {
    const effect = function reactiveEffect() {
      if (!effectStack.includes(effect)) {
        try {
          effectStack.push(effect);
          activeEffect = effect;
          fn();
        } finally {
          effectStack.pop();
          activeEffect = effectStack[effectStack.length - 1];
        }
      }
    };
    effect.deps = [];
    effect.options = options;
    return effect;
  }

  return {
    reactive,
    effect,
  };
})();
